src/st/st-icon-theme.c
subprojects/extensions-app/data/metainfo/org.gnome.Extensions.metainfo.xml.in
subprojects/extensions-app/data/org.gnome.Extensions.desktop.in.in
-subprojects/extensions-app/js/main.js
+subprojects/extensions-app/js/extensionRow.js
+subprojects/extensions-app/js/extensionsWindow.js
subprojects/extensions-app/data/ui/extension-row.ui
subprojects/extensions-app/data/ui/extensions-window.ui
subprojects/extensions-app/data/ui/help-overlay.ui
--- /dev/null
+import Adw from 'gi://Adw?version=1';
+import GLib from 'gi://GLib';
+import Gio from 'gi://Gio';
+import GObject from 'gi://GObject';
+
+import {ExtensionState, ExtensionType, deserializeExtension} from './misc/extensionUtils.js';
+
+export const ExtensionRow = GObject.registerClass({
+ GTypeName: 'ExtensionRow',
+ Template: 'resource:///org/gnome/Extensions/ui/extension-row.ui',
+ InternalChildren: [
+ 'detailsPopover',
+ 'descriptionLabel',
+ 'versionLabel',
+ 'errorLabel',
+ 'errorButton',
+ 'updatesButton',
+ 'switch',
+ 'actionsBox',
+ ],
+}, class ExtensionRow extends Adw.ActionRow {
+ _init(extension) {
+ super._init();
+
+ this._app = Gio.Application.get_default();
+ this._extension = extension;
+
+ [this._keywords] = GLib.str_tokenize_and_fold(this.name, null);
+
+ this._actionGroup = new Gio.SimpleActionGroup();
+ this.insert_action_group('row', this._actionGroup);
+
+ let action;
+ action = new Gio.SimpleAction({
+ name: 'show-prefs',
+ enabled: this.hasPrefs,
+ });
+ action.connect('activate', () => {
+ this._detailsPopover.popdown();
+ this.get_root().openPrefs(this.uuid);
+ });
+ this._actionGroup.add_action(action);
+
+ action = new Gio.SimpleAction({
+ name: 'show-url',
+ enabled: this.url !== '',
+ });
+ action.connect('activate', () => {
+ this._detailsPopover.popdown();
+ Gio.AppInfo.launch_default_for_uri(
+ this.url, this.get_display().get_app_launch_context());
+ });
+ this._actionGroup.add_action(action);
+
+ action = new Gio.SimpleAction({
+ name: 'uninstall',
+ enabled: this.type === ExtensionType.PER_USER,
+ });
+ action.connect('activate', () => {
+ this._detailsPopover.popdown();
+ this.get_root().uninstall(this.uuid);
+ });
+ this._actionGroup.add_action(action);
+
+ action = new Gio.SimpleAction({
+ name: 'enabled',
+ state: new GLib.Variant('b', false),
+ });
+ action.connect('activate', () => {
+ const state = action.get_state();
+ action.change_state(new GLib.Variant('b', !state.get_boolean()));
+ });
+
+ action.connect('change-state', (a, state) => {
+ if (state.get_boolean())
+ this._app.shellProxy.EnableExtensionAsync(this.uuid).catch(console.error);
+ else
+ this._app.shellProxy.DisableExtensionAsync(this.uuid).catch(console.error);
+ });
+ this._actionGroup.add_action(action);
+
+ this.title = this.name;
+
+ const desc = this._extension.metadata.description.split('\n')[0];
+ this._descriptionLabel.label = desc;
+
+ this.connect('destroy', this._onDestroy.bind(this));
+
+ this._extensionStateChangedId = this._app.shellProxy.connectSignal(
+ 'ExtensionStateChanged', (p, sender, [uuid, newState]) => {
+ if (this.uuid !== uuid)
+ return;
+
+ this._extension = deserializeExtension(newState);
+ this._updateState();
+ });
+ this._updateState();
+ }
+
+ get uuid() {
+ return this._extension.uuid;
+ }
+
+ get name() {
+ return this._extension.metadata.name;
+ }
+
+ get hasPrefs() {
+ return this._extension.hasPrefs;
+ }
+
+ get hasUpdate() {
+ return this._extension.hasUpdate || false;
+ }
+
+ get hasError() {
+ const {state} = this._extension;
+ return state === ExtensionState.OUT_OF_DATE ||
+ state === ExtensionState.ERROR;
+ }
+
+ get type() {
+ return this._extension.type;
+ }
+
+ get creator() {
+ return this._extension.metadata.creator || '';
+ }
+
+ get url() {
+ return this._extension.metadata.url || '';
+ }
+
+ get version() {
+ return this._extension.metadata['version-name'] || this._extension.metadata.version || '';
+ }
+
+ get error() {
+ if (!this.hasError)
+ return '';
+
+ if (this._extension.state === ExtensionState.OUT_OF_DATE) {
+ const {ShellVersion: shellVersion} = this._app.shellProxy;
+ return this.version !== ''
+ ? _('The installed version of this extension (%s) is incompatible with the current version of GNOME (%s). The extension has been disabled.').format(this.version, shellVersion)
+ : _('The installed version of this extension is incompatible with the current version of GNOME (%s). The extension has been disabled.').format(shellVersion);
+ }
+
+ const message = [
+ _('An error has occurred in this extension. This could cause issues elsewhere in the system. It is recommended to turn the extension off until the error is resolved.'),
+ ];
+
+ if (this._extension.error) {
+ message.push(
+ // translators: Details for an extension error
+ _('Error details:'), this._extension.error);
+ }
+
+ return message.join('\n\n');
+ }
+
+ get keywords() {
+ return this._keywords;
+ }
+
+ _updateState() {
+ const state = this._extension.state === ExtensionState.ENABLED;
+
+ const action = this._actionGroup.lookup_action('enabled');
+ action.set_state(new GLib.Variant('b', state));
+ action.enabled = this._extension.canChange;
+
+ if (!action.enabled)
+ this._switch.active = state;
+
+ this._updatesButton.visible = this.hasUpdate;
+ this._errorButton.visible = this.hasError;
+ this._errorLabel.label = this.error;
+
+ this._versionLabel.label = _('Version %s').format(this.version.toString());
+ this._versionLabel.visible = this.version !== '';
+ }
+
+ _onDestroy() {
+ if (!this._app.shellProxy)
+ return;
+
+ if (this._extensionStateChangedId)
+ this._app.shellProxy.disconnectSignal(this._extensionStateChangedId);
+ this._extensionStateChangedId = 0;
+ }
+});
+
--- /dev/null
+import Adw from 'gi://Adw?version=1';
+import GLib from 'gi://GLib';
+import Gio from 'gi://Gio';
+import GObject from 'gi://GObject';
+import Gtk from 'gi://Gtk?version=4.0';
+import Shew from 'gi://Shew';
+
+const Package = imports.package;
+import * as Gettext from 'gettext';
+
+import * as Config from './misc/config.js';
+import {ExtensionRow} from './extensionRow.js';
+import {ExtensionState, ExtensionType, deserializeExtension} from './misc/extensionUtils.js';
+
+Gio._promisify(Gio.DBusConnection.prototype, 'call');
+Gio._promisify(Shew.WindowExporter.prototype, 'export');
+
+export const ExtensionsWindow = GObject.registerClass({
+ GTypeName: 'ExtensionsWindow',
+ Template: 'resource:///org/gnome/Extensions/ui/extensions-window.ui',
+ InternalChildren: [
+ 'userGroup',
+ 'userList',
+ 'systemGroup',
+ 'systemList',
+ 'mainStack',
+ 'searchBar',
+ 'searchButton',
+ 'searchEntry',
+ 'updatesBanner',
+ ],
+}, class ExtensionsWindow extends Adw.ApplicationWindow {
+ _init(params) {
+ super._init(params);
+
+ if (Config.PROFILE === 'development')
+ this.add_css_class('devel');
+
+ this._updatesCheckId = 0;
+
+ this._exporter = new Shew.WindowExporter({window: this});
+ this._exportedHandle = '';
+
+ this.add_action_entries(
+ [{
+ name: 'show-about',
+ activate: () => this._showAbout(),
+ }, {
+ name: 'logout',
+ activate: () => this._logout(),
+ }, {
+ name: 'user-extensions-enabled',
+ state: 'false',
+ change_state: (a, state) => {
+ this._shellProxy.UserExtensionsEnabled = state.get_boolean();
+ },
+ }]);
+
+ this._searchTerms = [];
+ this._searchEntry.connect('search-changed', () => {
+ const {text} = this._searchEntry;
+ if (text === '')
+ this._searchTerms = [];
+ else
+ [this._searchTerms] = GLib.str_tokenize_and_fold(text, null);
+
+ this._userList.invalidate_filter();
+ this._systemList.invalidate_filter();
+ });
+
+ this._userList.set_sort_func(this._sortList.bind(this));
+ this._userList.set_filter_func(this._filterList.bind(this));
+ this._userList.set_placeholder(new Gtk.Label({
+ label: _('No Matches'),
+ margin_start: 12,
+ margin_end: 12,
+ margin_top: 12,
+ margin_bottom: 12,
+ }));
+ this._userList.connect('row-activated', (_list, row) => row.activate());
+
+ this._systemList.set_sort_func(this._sortList.bind(this));
+ this._systemList.set_filter_func(this._filterList.bind(this));
+ this._systemList.set_placeholder(new Gtk.Label({
+ label: _('No Matches'),
+ margin_start: 12,
+ margin_end: 12,
+ margin_top: 12,
+ margin_bottom: 12,
+ }));
+ this._systemList.connect('row-activated', (_list, row) => row.activate());
+
+ this._shellProxy.connectSignal('ExtensionStateChanged',
+ this._onExtensionStateChanged.bind(this));
+
+ this._shellProxy.connect('g-properties-changed',
+ this._onUserExtensionsEnabledChanged.bind(this));
+ this._onUserExtensionsEnabledChanged();
+
+ this._scanExtensions();
+ }
+
+ get _shellProxy() {
+ return this.application.shellProxy;
+ }
+
+ uninstall(uuid) {
+ const row = this._findExtensionRow(uuid);
+
+ const dialog = new Gtk.MessageDialog({
+ transient_for: this,
+ modal: true,
+ text: _('Remove “%s”?').format(row.name),
+ secondary_text: _('If you remove the extension, you need to return to download it if you want to enable it again'),
+ });
+
+ dialog.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL);
+ dialog.add_button(_('_Remove'), Gtk.ResponseType.ACCEPT)
+ .get_style_context().add_class('destructive-action');
+
+ dialog.connect('response', (dlg, response) => {
+ if (response === Gtk.ResponseType.ACCEPT)
+ this._shellProxy.UninstallExtensionAsync(uuid).catch(console.error);
+ dialog.destroy();
+ });
+ dialog.present();
+ }
+
+ async openPrefs(uuid) {
+ if (!this._exportedHandle) {
+ try {
+ this._exportedHandle = await this._exporter.export();
+ } catch (e) {
+ console.warn(`Failed to export window: ${e.message}`);
+ }
+ }
+
+ this._shellProxy.OpenExtensionPrefsAsync(uuid,
+ this._exportedHandle,
+ {modal: new GLib.Variant('b', true)}).catch(console.error);
+ }
+
+ _showAbout() {
+ const aboutWindow = new Adw.AboutWindow({
+ developers: [
+ 'Florian Müllner <fmuellner@gnome.org>',
+ 'Jasper St. Pierre <jstpierre@mecheye.net>',
+ 'Didier Roche <didrocks@ubuntu.com>',
+ 'Romain Vigier <contact@romainvigier.fr>',
+ ],
+ designers: [
+ 'Allan Day <allanpday@gmail.com>',
+ 'Tobias Bernard <tbernard@gnome.org>',
+ ],
+ translator_credits: _('translator-credits'),
+ application_name: _('Extensions'),
+ license_type: Gtk.License.GPL_2_0,
+ application_icon: Package.name,
+ version: Package.version,
+ developer_name: _('The GNOME Project'),
+ website: 'https://apps.gnome.org/app/org.gnome.Extensions/',
+ issue_url: 'https://gitlab.gnome.org/GNOME/gnome-shell/issues/new',
+
+ transient_for: this,
+ });
+ aboutWindow.present();
+ }
+
+ _logout() {
+ this.application.get_dbus_connection().call(
+ 'org.gnome.SessionManager',
+ '/org/gnome/SessionManager',
+ 'org.gnome.SessionManager',
+ 'Logout',
+ new GLib.Variant('(u)', [0]),
+ null,
+ Gio.DBusCallFlags.NONE,
+ -1,
+ null);
+ }
+
+ _sortList(row1, row2) {
+ return row1.name.localeCompare(row2.name);
+ }
+
+ _filterList(row) {
+ return this._searchTerms.every(
+ t => row.keywords.some(k => k.startsWith(t)));
+ }
+
+ _findExtensionRow(uuid) {
+ return [
+ ...this._userList,
+ ...this._systemList,
+ ].find(c => c.uuid === uuid);
+ }
+
+ _onUserExtensionsEnabledChanged() {
+ const action = this.lookup_action('user-extensions-enabled');
+ action.set_state(
+ new GLib.Variant('b', this._shellProxy.UserExtensionsEnabled));
+ }
+
+ _onExtensionStateChanged(proxy, senderName, [uuid, newState]) {
+ const extension = deserializeExtension(newState);
+ let row = this._findExtensionRow(uuid);
+
+ this._queueUpdatesCheck();
+
+ // the extension's type changed; remove the corresponding row
+ // and reset the variable to null so that we create a new row
+ // below and add it to the appropriate list
+ if (row && row.type !== extension.type) {
+ row.get_parent().remove(row);
+ row = null;
+ }
+
+ if (row) {
+ if (extension.state === ExtensionState.UNINSTALLED)
+ row.get_parent().remove(row);
+ } else {
+ this._addExtensionRow(extension);
+ }
+
+ this._syncListVisibility();
+ }
+
+ async _scanExtensions() {
+ try {
+ const [extensionsMap] = await this._shellProxy.ListExtensionsAsync();
+
+ for (let uuid in extensionsMap) {
+ const extension = deserializeExtension(extensionsMap[uuid]);
+ this._addExtensionRow(extension);
+ }
+ this._extensionsLoaded();
+ } catch (e) {
+ if (e instanceof Gio.DBusError) {
+ console.log(`Failed to connect to shell proxy: ${e}`);
+ this._mainStack.visible_child_name = 'noshell';
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ _addExtensionRow(extension) {
+ const row = new ExtensionRow(extension);
+
+ if (row.type === ExtensionType.PER_USER)
+ this._userList.append(row);
+ else
+ this._systemList.append(row);
+ }
+
+ _queueUpdatesCheck() {
+ if (this._updatesCheckId)
+ return;
+
+ this._updatesCheckId = GLib.timeout_add_seconds(
+ GLib.PRIORITY_DEFAULT, 1, () => {
+ this._checkUpdates();
+
+ this._updatesCheckId = 0;
+ return GLib.SOURCE_REMOVE;
+ });
+ }
+
+ _syncListVisibility() {
+ this._userGroup.visible = [...this._userList].length > 1;
+ this._systemGroup.visible = [...this._systemList].length > 1;
+
+ if (this._userGroup.visible || this._systemGroup.visible)
+ this._mainStack.visible_child_name = 'main';
+ else
+ this._mainStack.visible_child_name = 'placeholder';
+ }
+
+ _checkUpdates() {
+ const nUpdates = [...this._userList].filter(c => c.hasUpdate).length;
+
+ this._updatesBanner.title = Gettext.ngettext(
+ '%d extension will be updated on next login.',
+ '%d extensions will be updated on next login.',
+ nUpdates).format(nUpdates);
+ this._updatesBanner.revealed = nUpdates > 0;
+ }
+
+ _extensionsLoaded() {
+ this._syncListVisibility();
+ this._checkUpdates();
+ }
+});
import GLib from 'gi://GLib';
import Gio from 'gi://Gio';
import GObject from 'gi://GObject';
-import Gtk from 'gi://Gtk?version=4.0';
-import Shew from 'gi://Shew';
import {setConsoleLogDomain} from 'console';
-import * as Gettext from 'gettext';
const Package = imports.package;
Package.initFormat();
-import * as Config from './misc/config.js';
-import * as ExtensionUtils from './misc/extensionUtils.js';
-
-import {ExtensionState, ExtensionType} from './misc/extensionUtils.js';
+import {ExtensionsWindow} from './extensionsWindow.js';
const GnomeShellIface = loadInterfaceXML('org.gnome.Shell.Extensions');
const GnomeShellProxy = Gio.DBusProxy.makeProxyWrapper(GnomeShellIface);
-Gio._promisify(Gio.DBusConnection.prototype, 'call');
-Gio._promisify(Shew.WindowExporter.prototype, 'export');
-
function loadInterfaceXML(iface) {
const uri = `resource:///org/gnome/Extensions/dbus-interfaces/${iface}.xml`;
const f = Gio.File.new_for_uri(uri);
return null;
}
-function toggleState(action) {
- let state = action.get_state();
- action.change_state(new GLib.Variant('b', !state.get_boolean()));
-}
-
var Application = GObject.registerClass(
class Application extends Adw.Application {
_init() {
}
});
-var ExtensionsWindow = GObject.registerClass({
- GTypeName: 'ExtensionsWindow',
- Template: 'resource:///org/gnome/Extensions/ui/extensions-window.ui',
- InternalChildren: [
- 'userGroup',
- 'userList',
- 'systemGroup',
- 'systemList',
- 'mainStack',
- 'searchBar',
- 'searchButton',
- 'searchEntry',
- 'updatesBanner',
- ],
-}, class ExtensionsWindow extends Adw.ApplicationWindow {
- _init(params) {
- super._init(params);
-
- if (Config.PROFILE === 'development')
- this.add_css_class('devel');
-
- this._updatesCheckId = 0;
-
- this._exporter = new Shew.WindowExporter({window: this});
- this._exportedHandle = '';
-
- this.add_action_entries(
- [{
- name: 'show-about',
- activate: () => this._showAbout(),
- }, {
- name: 'logout',
- activate: () => this._logout(),
- }, {
- name: 'user-extensions-enabled',
- state: 'false',
- change_state: (a, state) => {
- this._shellProxy.UserExtensionsEnabled = state.get_boolean();
- },
- }]);
-
- this._searchTerms = [];
- this._searchEntry.connect('search-changed', () => {
- const {text} = this._searchEntry;
- if (text === '')
- this._searchTerms = [];
- else
- [this._searchTerms] = GLib.str_tokenize_and_fold(text, null);
-
- this._userList.invalidate_filter();
- this._systemList.invalidate_filter();
- });
-
- this._userList.set_sort_func(this._sortList.bind(this));
- this._userList.set_filter_func(this._filterList.bind(this));
- this._userList.set_placeholder(new Gtk.Label({
- label: _('No Matches'),
- margin_start: 12,
- margin_end: 12,
- margin_top: 12,
- margin_bottom: 12,
- }));
- this._userList.connect('row-activated', (_list, row) => row.activate());
-
- this._systemList.set_sort_func(this._sortList.bind(this));
- this._systemList.set_filter_func(this._filterList.bind(this));
- this._systemList.set_placeholder(new Gtk.Label({
- label: _('No Matches'),
- margin_start: 12,
- margin_end: 12,
- margin_top: 12,
- margin_bottom: 12,
- }));
- this._systemList.connect('row-activated', (_list, row) => row.activate());
-
- this._shellProxy.connectSignal('ExtensionStateChanged',
- this._onExtensionStateChanged.bind(this));
-
- this._shellProxy.connect('g-properties-changed',
- this._onUserExtensionsEnabledChanged.bind(this));
- this._onUserExtensionsEnabledChanged();
-
- this._scanExtensions();
- }
-
- get _shellProxy() {
- return this.application.shellProxy;
- }
-
- uninstall(uuid) {
- let row = this._findExtensionRow(uuid);
-
- let dialog = new Gtk.MessageDialog({
- transient_for: this,
- modal: true,
- text: _('Remove “%s”?').format(row.name),
- secondary_text: _('If you remove the extension, you need to return to download it if you want to enable it again'),
- });
-
- dialog.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL);
- dialog.add_button(_('_Remove'), Gtk.ResponseType.ACCEPT)
- .get_style_context().add_class('destructive-action');
-
- dialog.connect('response', (dlg, response) => {
- if (response === Gtk.ResponseType.ACCEPT)
- this._shellProxy.UninstallExtensionAsync(uuid).catch(console.error);
- dialog.destroy();
- });
- dialog.present();
- }
-
- async openPrefs(uuid) {
- if (!this._exportedHandle) {
- try {
- this._exportedHandle = await this._exporter.export();
- } catch (e) {
- console.warn(`Failed to export window: ${e.message}`);
- }
- }
-
- this._shellProxy.OpenExtensionPrefsAsync(uuid,
- this._exportedHandle,
- {modal: new GLib.Variant('b', true)}).catch(console.error);
- }
-
- _showAbout() {
- let aboutWindow = new Adw.AboutWindow({
- developers: [
- 'Florian Müllner <fmuellner@gnome.org>',
- 'Jasper St. Pierre <jstpierre@mecheye.net>',
- 'Didier Roche <didrocks@ubuntu.com>',
- 'Romain Vigier <contact@romainvigier.fr>',
- ],
- designers: [
- 'Allan Day <allanpday@gmail.com>',
- 'Tobias Bernard <tbernard@gnome.org>',
- ],
- translator_credits: _('translator-credits'),
- application_name: _('Extensions'),
- license_type: Gtk.License.GPL_2_0,
- application_icon: Package.name,
- version: Package.version,
- developer_name: _('The GNOME Project'),
- website: 'https://apps.gnome.org/app/org.gnome.Extensions/',
- issue_url: 'https://gitlab.gnome.org/GNOME/gnome-shell/issues/new',
-
- transient_for: this,
- });
- aboutWindow.present();
- }
-
- _logout() {
- this.application.get_dbus_connection().call(
- 'org.gnome.SessionManager',
- '/org/gnome/SessionManager',
- 'org.gnome.SessionManager',
- 'Logout',
- new GLib.Variant('(u)', [0]),
- null,
- Gio.DBusCallFlags.NONE,
- -1,
- null);
- }
-
- _sortList(row1, row2) {
- return row1.name.localeCompare(row2.name);
- }
-
- _filterList(row) {
- return this._searchTerms.every(
- t => row.keywords.some(k => k.startsWith(t)));
- }
-
- _findExtensionRow(uuid) {
- return [
- ...this._userList,
- ...this._systemList,
- ].find(c => c.uuid === uuid);
- }
-
- _onUserExtensionsEnabledChanged() {
- let action = this.lookup_action('user-extensions-enabled');
- action.set_state(
- new GLib.Variant('b', this._shellProxy.UserExtensionsEnabled));
- }
-
- _onExtensionStateChanged(proxy, senderName, [uuid, newState]) {
- let extension = ExtensionUtils.deserializeExtension(newState);
- let row = this._findExtensionRow(uuid);
-
- this._queueUpdatesCheck();
-
- // the extension's type changed; remove the corresponding row
- // and reset the variable to null so that we create a new row
- // below and add it to the appropriate list
- if (row && row.type !== extension.type) {
- row.get_parent().remove(row);
- row = null;
- }
-
- if (row) {
- if (extension.state === ExtensionState.UNINSTALLED)
- row.get_parent().remove(row);
- } else {
- this._addExtensionRow(extension);
- }
-
- this._syncListVisibility();
- }
-
- async _scanExtensions() {
- try {
- const [extensionsMap] = await this._shellProxy.ListExtensionsAsync();
-
- for (let uuid in extensionsMap) {
- let extension = ExtensionUtils.deserializeExtension(extensionsMap[uuid]);
- this._addExtensionRow(extension);
- }
- this._extensionsLoaded();
- } catch (e) {
- if (e instanceof Gio.DBusError) {
- console.log(`Failed to connect to shell proxy: ${e}`);
- this._mainStack.visible_child_name = 'noshell';
- } else {
- throw e;
- }
- }
- }
-
- _addExtensionRow(extension) {
- let row = new ExtensionRow(extension);
-
- if (row.type === ExtensionType.PER_USER)
- this._userList.append(row);
- else
- this._systemList.append(row);
- }
-
- _queueUpdatesCheck() {
- if (this._updatesCheckId)
- return;
-
- this._updatesCheckId = GLib.timeout_add_seconds(
- GLib.PRIORITY_DEFAULT, 1, () => {
- this._checkUpdates();
-
- this._updatesCheckId = 0;
- return GLib.SOURCE_REMOVE;
- });
- }
-
- _syncListVisibility() {
- this._userGroup.visible = [...this._userList].length > 1;
- this._systemGroup.visible = [...this._systemList].length > 1;
-
- if (this._userGroup.visible || this._systemGroup.visible)
- this._mainStack.visible_child_name = 'main';
- else
- this._mainStack.visible_child_name = 'placeholder';
- }
-
- _checkUpdates() {
- let nUpdates = [...this._userList].filter(c => c.hasUpdate).length;
-
- this._updatesBanner.title = Gettext.ngettext(
- '%d extension will be updated on next login.',
- '%d extensions will be updated on next login.',
- nUpdates).format(nUpdates);
- this._updatesBanner.revealed = nUpdates > 0;
- }
-
- _extensionsLoaded() {
- this._syncListVisibility();
- this._checkUpdates();
- }
-});
-
-var ExtensionRow = GObject.registerClass({
- GTypeName: 'ExtensionRow',
- Template: 'resource:///org/gnome/Extensions/ui/extension-row.ui',
- InternalChildren: [
- 'detailsPopover',
- 'descriptionLabel',
- 'versionLabel',
- 'errorLabel',
- 'errorButton',
- 'updatesButton',
- 'switch',
- 'actionsBox',
- ],
-}, class ExtensionRow extends Adw.ActionRow {
- _init(extension) {
- super._init();
-
- this._app = Gio.Application.get_default();
- this._extension = extension;
- this._prefsModule = null;
-
- [this._keywords] = GLib.str_tokenize_and_fold(this.name, null);
-
- this._actionGroup = new Gio.SimpleActionGroup();
- this.insert_action_group('row', this._actionGroup);
-
- let action;
- action = new Gio.SimpleAction({
- name: 'show-prefs',
- enabled: this.hasPrefs,
- });
- action.connect('activate', () => {
- this._detailsPopover.popdown();
- this.get_root().openPrefs(this.uuid);
- });
- this._actionGroup.add_action(action);
-
- action = new Gio.SimpleAction({
- name: 'show-url',
- enabled: this.url !== '',
- });
- action.connect('activate', () => {
- this._detailsPopover.popdown();
- Gio.AppInfo.launch_default_for_uri(
- this.url, this.get_display().get_app_launch_context());
- });
- this._actionGroup.add_action(action);
-
- action = new Gio.SimpleAction({
- name: 'uninstall',
- enabled: this.type === ExtensionType.PER_USER,
- });
- action.connect('activate', () => {
- this._detailsPopover.popdown();
- this.get_root().uninstall(this.uuid);
- });
- this._actionGroup.add_action(action);
-
- action = new Gio.SimpleAction({
- name: 'enabled',
- state: new GLib.Variant('b', false),
- });
- action.connect('activate', toggleState);
- action.connect('change-state', (a, state) => {
- if (state.get_boolean())
- this._app.shellProxy.EnableExtensionAsync(this.uuid).catch(console.error);
- else
- this._app.shellProxy.DisableExtensionAsync(this.uuid).catch(console.error);
- });
- this._actionGroup.add_action(action);
-
- this.title = this.name;
-
- const desc = this._extension.metadata.description.split('\n')[0];
- this._descriptionLabel.label = desc;
-
- this.connect('destroy', this._onDestroy.bind(this));
-
- this._extensionStateChangedId = this._app.shellProxy.connectSignal(
- 'ExtensionStateChanged', (p, sender, [uuid, newState]) => {
- if (this.uuid !== uuid)
- return;
-
- this._extension = ExtensionUtils.deserializeExtension(newState);
- this._updateState();
- });
- this._updateState();
- }
-
- get uuid() {
- return this._extension.uuid;
- }
-
- get name() {
- return this._extension.metadata.name;
- }
-
- get hasPrefs() {
- return this._extension.hasPrefs;
- }
-
- get hasUpdate() {
- return this._extension.hasUpdate || false;
- }
-
- get hasError() {
- const {state} = this._extension;
- return state === ExtensionState.OUT_OF_DATE ||
- state === ExtensionState.ERROR;
- }
-
- get type() {
- return this._extension.type;
- }
-
- get creator() {
- return this._extension.metadata.creator || '';
- }
-
- get url() {
- return this._extension.metadata.url || '';
- }
-
- get version() {
- return this._extension.metadata['version-name'] || this._extension.metadata.version || '';
- }
-
- get error() {
- if (!this.hasError)
- return '';
-
- if (this._extension.state === ExtensionState.OUT_OF_DATE) {
- const {ShellVersion: shellVersion} = this._app.shellProxy;
- return this.version !== ''
- ? _('The installed version of this extension (%s) is incompatible with the current version of GNOME (%s). The extension has been disabled.').format(this.version, shellVersion)
- : _('The installed version of this extension is incompatible with the current version of GNOME (%s). The extension has been disabled.').format(shellVersion);
- }
-
- const message = [
- _('An error has occurred in this extension. This could cause issues elsewhere in the system. It is recommended to turn the extension off until the error is resolved.'),
- ];
-
- if (this._extension.error) {
- message.push(
- // translators: Details for an extension error
- _('Error details:'), this._extension.error);
- }
-
- return message.join('\n\n');
- }
-
- get keywords() {
- return this._keywords;
- }
-
- _updateState() {
- let state = this._extension.state === ExtensionState.ENABLED;
-
- let action = this._actionGroup.lookup('enabled');
- action.set_state(new GLib.Variant('b', state));
- action.enabled = this._canToggle();
-
- if (!action.enabled)
- this._switch.active = state;
-
- this._updatesButton.visible = this.hasUpdate;
- this._errorButton.visible = this.hasError;
- this._errorLabel.label = this.error;
-
- this._versionLabel.label = _('Version %s').format(this.version.toString());
- this._versionLabel.visible = this.version !== '';
- }
-
- _onDestroy() {
- if (!this._app.shellProxy)
- return;
-
- if (this._extensionStateChangedId)
- this._app.shellProxy.disconnectSignal(this._extensionStateChangedId);
- this._extensionStateChangedId = 0;
- }
-
- _canToggle() {
- return this._extension.canChange;
- }
-});
-
/**
* Main entrypoint for the app
*
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/org/gnome/Extensions@profile@/js">
+ <file>extensionRow.js</file>
+ <file>extensionsWindow.js</file>
<file>main.js</file>
<file>misc/config.js</file>